package cn.felord.socket.autoconfiguration;

import cn.felord.socket.api.WsController;
import cn.felord.socket.messaging.ClientRepository;
import cn.felord.socket.messaging.NameSpaceWrapper;
import cn.felord.socket.messaging.SimpleClientRepository;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIONamespace;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * The type Socket io auto configuration.
 *
 * @author dax
 * @since 2019 /4/19 0:09
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(SocketIoProperties.class)
public class SocketIoAutoConfiguration {
    @Resource
    private SocketIoProperties socketIoProperties;

    /**
     * Client repository client repository.
     *
     * @return the client repository
     */
    @Bean
    public ClientRepository clientRepository() {
        return new SimpleClientRepository();
    }

    /**
     * Socket io server socket io server.
     *
     * @param clientRepository the client repository
     * @return the socket io server
     */
    @Bean
    public SocketIOServer socketIOServer(ClientRepository clientRepository) {
        SocketConfig socketConfig = new SocketConfig();
        socketConfig.setTcpNoDelay(true);
        socketConfig.setSoLinger(0);
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        config.setSocketConfig(socketConfig);
        config.setHostname(socketIoProperties.getHost());
        config.setPort(socketIoProperties.getPort());
        config.setBossThreads(socketIoProperties.getBossCount());
        config.setWorkerThreads(socketIoProperties.getWorkerThreads());
        config.setAllowCustomRequests(socketIoProperties.isAllowCustomRequests());
        config.setUpgradeTimeout(socketIoProperties.getWorkerThreads());
        config.setPingTimeout(socketIoProperties.getPingTimeout());
        config.setPingInterval(socketIoProperties.getPingInterval());
        SocketIOServer socketIOServer = new SocketIOServer(config);
        // 断线后移除客户端
        socketIOServer.addDisconnectListener(client -> {
            String username = client.getHandshakeData().getSingleUrlParam(socketIoProperties.getClientKey());
            clientRepository.remove(username);
            client.disconnect();
            log.info("client {} disconnected", client.getSessionId().toString());
        });
        return socketIOServer;
    }


    /**
     * 配置路径监听.
     *
     * @param socketIOServer   the socket io server
     * @param clientRepository the client repository
     * @return the name space wrapper
     */
    @Bean
    public NameSpaceWrapper nameSpaceWrapper(SocketIOServer socketIOServer, ClientRepository clientRepository) {
        SocketIONamespace socketIONamespace = socketIOServer.addNamespace("/test");

        socketIONamespace.addConnectListener(client -> {
            String username = client.getHandshakeData().getSingleUrlParam(socketIoProperties.getClientKey());
            if (StringUtils.hasText(username)) {
                clientRepository.add(username, client);
            }
        });
        return new NameSpaceWrapper() {
            @Override
            public SocketIONamespace namespace() {
                return socketIONamespace;
            }

            @Override
            public Object annotationListeners() {
                return new WsController();
            }
        };
    }

    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketIOServer) {
        return new SpringAnnotationScanner(socketIOServer);
    }


    /**
     * Socket io server 生命周期处理.
     *
     * @param socketIOServer   the socket io server
     * @param clientRepository the client repository
     * @return the smart lifecycle
     */
    @Bean
    public SmartLifecycle socketIOServerLifecycle(SocketIOServer socketIOServer, ClientRepository clientRepository) {
        return new SmartLifecycle() {
            AtomicBoolean isRunning = new AtomicBoolean();

            @Override
            public void start() {
                if (Objects.nonNull(socketIOServer)) {
                    socketIOServer.start();
                    isRunning.compareAndSet(false, true);
                    log.info(" socketServer is started");
                }
            }

            @Override
            public void stop() {
                if (Objects.nonNull(socketIOServer)) {
                    socketIOServer.stop();
                    clientRepository.removeAll();
                    isRunning.compareAndSet(true, false);
                    log.info(" socketServer is stop");
                }
            }

            @Override
            public boolean isRunning() {
                return isRunning.get();
            }
        };
    }
}
